#include "qemu/osdep.h"
#include "qemu/module.h"
#include "hw/i2c/i2c.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "qapi/visitor.h"
#include "trace.h"
#include "chardev/char-fe.h"
#include "qemu/error-report.h"

#define TYPE_EEPROM_I2CSLAVE "eeprom-i2c-slave"
#define EEPROM_I2CSLAVE(obj) OBJECT_CHECK(slave_state, (obj), TYPE_EEPROM_I2CSLAVE)
#define I2C_PATH "./output/data/mockdata/i2c/"

typedef struct {
    I2CSlave parent_obj;
    CharBackend chrbe;
    uint32_t offset;
    uint32_t high;
    uint32_t low;
    char *path;
    FILE *p;
    uint32_t reg_bytes;
    uint32_t reg_temp;
    bool address_byte;
    bool transfer_start;
    bool transfer_recv;
    bool buffer_flag; // 缓存是否需要修改的标志
    uint8_t reg_id;
    uint8_t channel_id;
    int buffer[20000];
	time_t last_modify;
	struct stat file_stat;
} slave_state;

static int eeprom_i2cslave_event(I2CSlave *i2c, enum i2c_event event)
{
    slave_state *s = EEPROM_I2CSLAVE(i2c);

    switch (event) {
    /* 数据传输开始，打开文件 */
        case I2C_START_SEND:
			if (s->channel_id == (uint8_t)-1){
				s->path = g_strdup_printf("%s%d/0xae_eeprom.bin", I2C_PATH, s->reg_id);
			}
			else{
				s->path = g_strdup_printf("%s%d/0xe0/%d/0xae_eeprom.bin", I2C_PATH, s->reg_id, s->channel_id);
			}
			if (stat(s->path, &s->file_stat) != 0){
				error_report("Get timestamp from %s failed;\n", s->path);
			}
			else if (s->file_stat.st_mtime != s->last_modify){
				warn_report("Detect %s changed, Reload buffer;\n", s->path);
				s->buffer_flag = false;
				s->last_modify = s->file_stat.st_mtime;
			}
            s->transfer_start = true;
            s->transfer_recv = true;
            s->address_byte = true;
            s->reg_bytes = 0;
            break;
        case I2C_START_RECV:
            s->transfer_start = true;
            s->transfer_recv = false;
            s->address_byte = true;
            s->reg_bytes = 0;
            break;
    /* 数据传输结束，关闭文件 */
        case I2C_FINISH:
            s->transfer_start = false;
            s->offset = 0;
            s->high = 0;
            s->low = 0;
            if (s->p != NULL) {
                s->buffer_flag = false;
                g_free(s->path);
                int ret = fclose(s->p);
                if (ret != 0) {
                    printf("i2c%d close eeprom file failed", s->reg_id);
                    return -1;
                }
                s->p = NULL;
            }
            break;
        case I2C_NACK:
            break;
    }
    return 0;
}

static void write_buffer(I2CSlave *i2c, int *buffer)
{
    slave_state *s = EEPROM_I2CSLAVE(i2c);
	memset(buffer, 0x00, 20000);
    if (s->channel_id == (uint8_t)-1){
        s->path = g_strdup_printf("%s%d/0xae_eeprom.bin", I2C_PATH, s->reg_id);
    }
    else{
        s->path = g_strdup_printf("%s%d/0xe0/%d/0xae_eeprom.bin", I2C_PATH, s->reg_id, s->channel_id);
    }
    s->p = fopen(s->path, "rb+");
    if (s->p == NULL) {
        printf("i2c%d open eeprom file failed", s->reg_id);
        return;
    }
    int ret = fseek(s->p, 0, SEEK_END);
    if (ret != 0) {
        printf("i2c%d fseek eeprom file failed\n", s->reg_id);
        return;
    }
    int file_size = ftell(s->p);
    ret = fseek(s->p, 0, SEEK_SET);
    if (ret != 0) {
        printf("i2c%d fseek eeprom file failed\n", s->reg_id);
        return;
    }
    for (int i = 0; i < file_size; i++) {
        ret = fread(buffer + i, 1, 1, s->p);
        if (ret == 0) {
            printf("i2c%d read eeprom file failed\n", s->reg_id);
            break;
        }
    }
    g_free(s->path);
    ret = fclose(s->p);
    if (ret != 0) {
        printf("i2c%d close eeprom file failed", s->reg_id);
    }
    s->buffer_flag = true;
    s->p = NULL;
}

static uint8_t eeprom_i2cslave_rx(I2CSlave *i2c)
{
    slave_state *s = EEPROM_I2CSLAVE(i2c);
    int rx = s->buffer[s->offset];
    s->offset++;
    return rx;
}

static int eeprom_i2cslave_tx(I2CSlave *i2c, uint8_t data)
{
    /* 前两个参数计算偏移量，后面的参数为要写入的数据 */
    slave_state *s = EEPROM_I2CSLAVE(i2c);
    if (s->buffer_flag == false) {
        write_buffer(i2c, s->buffer);
    }
    int offsetcount = 256;
    if (s->transfer_start && s->transfer_recv) {
        if (s->address_byte) {
            s->high = data;
            s->address_byte = false;
        } else {
            s->low = data;
            s->offset = s->high * offsetcount + s->low;
            s->transfer_recv = false;
        }
    } else if (s->transfer_start && !s->transfer_recv) {
        if (s->p == NULL) {
            if (s->channel_id == (uint8_t)-1){
                s->path = g_strdup_printf("%s%d/0xae_eeprom.bin", I2C_PATH, s->reg_id);
            }
            else{
                s->path = g_strdup_printf("%s%d/0xe0/%d/0xae_eeprom.bin", I2C_PATH, s->reg_id, s->channel_id);
            }
            s->p = fopen(s->path, "rb+");
            if (s->p == NULL) {
                printf("i2c%d open eeprom file failed", s->reg_id);
                return -1;
            }
        }
        int ret = fseek(s->p, s->offset, SEEK_SET);
        if (ret != 0) {
            return -1;
        }
        int buffer = data;
        ret = fwrite(&buffer, 1, 1, s->p);
        if (ret != 1) {
            printf("i2c%d write eeprom file failed", s->reg_id);
            return -1;
        }
        s->offset++;
    }
    return 0;
}

static void eeprom_i2cslave_reset(DeviceState *dev)
{
    slave_state *s = EEPROM_I2CSLAVE(dev);
    s->transfer_start = false;
}

static void eeprom_i2cslave_realize(DeviceState *dev, Error **errp)
{
    slave_state *s = EEPROM_I2CSLAVE(dev);
    s->transfer_recv = false;
}

static void generic_i2cslave_access_addr(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
{
    uint8_t *ptr = (uint8_t *)object_field_prop_ptr(obj, (Property *)opaque);
    visit_type_uint8(v, name, ptr, errp);
}

static const PropertyInfo generic_i2cslave_prop_regaddr = {
    .name = "uint8",
    .get = generic_i2cslave_access_addr,
    .set = generic_i2cslave_access_addr,
    // .realized_set_allowed = true,
};

static Property eeprom_i2cslave_properties[] = {
    DEFINE_PROP_UNSIGNED_NODEFAULT("reg-id", slave_state, reg_id, generic_i2cslave_prop_regaddr, uint8_t),
    DEFINE_PROP_UNSIGNED("channel-id", slave_state, channel_id, -1, qdev_prop_uint8, uint8_t),
    DEFINE_PROP_END_OF_LIST(),
};

static void eeprom_i2cslave_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
    k->event = eeprom_i2cslave_event;
    k->recv = eeprom_i2cslave_rx;
    k->send = eeprom_i2cslave_tx;
    dc->reset = eeprom_i2cslave_reset;
    dc->realize = eeprom_i2cslave_realize;
    device_class_set_props(dc, eeprom_i2cslave_properties);
}

static void eeprom_i2cslave_init(Object *obj)
{
    slave_state *s = EEPROM_I2CSLAVE(obj);
    s->transfer_start = false;
}

static const TypeInfo eeprom_i2cslave_info = {
    .name = TYPE_EEPROM_I2CSLAVE,
    .parent = TYPE_I2C_SLAVE,
    .instance_size = sizeof(slave_state),
    .class_init = eeprom_i2cslave_class_init,
    .instance_init = eeprom_i2cslave_init,
};

static void eeprom_i2cslave_register_types(void)
{
    type_register_static(&eeprom_i2cslave_info);
}

type_init(eeprom_i2cslave_register_types)